﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace 数据挖掘实验
{
    /// <summary>
    /// 贝叶斯分类器
    /// </summary>
    public class BayesClassifier
    {
        /// <summary>
        /// 贝叶斯分类结果信息
        /// </summary>
        public struct ResInfo
        {
            /// <summary>
            /// 结论值
            /// </summary>
            public string resVal;

            /// <summary>
            /// 特征对应的值
            /// </summary>
            public List<string> itemVals;

            /// <summary>
            /// 计算出几率的绝对百分比
            /// </summary>
            public float val;
        }

        // 每一列对应的特征值字典
        Dictionary<int, HashSet<string>> itemKeys = new Dictionary<int, HashSet<string>>();

        // 结论计数字典
        Dictionary<string, int> resCountDic = new Dictionary<string, int>();

        // 特征计数字典
        Dictionary<string, Dictionary<string, int>> resItemCountDic =
            new Dictionary<string, Dictionary<string, int>>();

        // 样本总数
        int amount;

        // 日志信息
        string logInfo = string.Empty;

        /// <summary>
        /// 结论标题
        /// </summary>
        public string ResultTitle => Titles[^1];

        /// <summary>
        /// 所有标题(特征与结论)
        /// </summary>
        public string[] Titles { get; private set; }

        /// <summary>
        /// 结论值
        /// </summary>
        public string[] ResultLabels { get; private set; }

        /// <summary>
        /// 日志输出事件
        /// </summary>
        public event Action<string, string> onLog;

        public bool Current { get; private set; }

        public void Open(string path)
        {
            using var reader = new StreamReader(File.Open(path, FileMode.Open), Encoding.GetEncoding("GB2312"));
            Analyze(reader);
        }

        /// <summary>
        /// 对样本文件进行分析
        /// </summary>
        /// <param name="reader"></param>
        public void Analyze(StreamReader reader)
        {
            // 初始化分类器
            amount = 0;
            itemKeys.Clear();
            resCountDic.Clear();
            resItemCountDic.Clear();
            string titleStr = reader.ReadLine(); // 读取第一行的标题信息
            Titles = titleStr.Split(',');
            Titles = Titles.Select(x => x.Trim()).ToArray(); // 提取标题

            string line;
            while ((line = reader.ReadLine()) != null) // 循环读取样本信息
            {
                string[] datas = line.Split(','); // 分割行文本 
                var resVal = datas[^1].Trim(); // 获取结论值(最后一项)
                if (!resCountDic.ContainsKey(resVal)) // 对结论值进行计数
                {
                    resCountDic.Add(resVal, 1);
                    resItemCountDic.Add(resVal, new Dictionary<string, int>());
                }
                else
                    resCountDic[resVal]++;

                // 计算结论值对应的条件(特征)数目
                var itemCount = resItemCountDic[resVal];
                for (int i = 1; i < datas.Length - 1; i++) // 从第一个到倒数第一个(略去编号和结论)
                {
                    string itemVal = datas[i].Trim();
                    if (!itemKeys.ContainsKey(i)) // 存储每一个特征的值(用于忽略判断)
                        itemKeys[i] = new HashSet<string>();
                    itemKeys[i].Add(itemVal);

                    if (!itemCount.ContainsKey(itemVal)) // 对结论对应的特征进行计数
                    {
                        itemCount.Add(itemVal, 1);
                    }
                    else
                        itemCount[itemVal]++;
                }

                amount++;
            }

            ResultLabels = resCountDic.Keys.Select(x => x.ToString()).ToArray(); // 提取所有结论值作为结论值标签

            // 填充未识别项(解决忽略值BUG)
            for (int i = 1; i < itemKeys.Count; i++)
            {
                var keys = itemKeys[i];
                foreach (var resVal in resCountDic.Keys)
                {
                    var itemCount = resItemCountDic[resVal];
                    foreach (var key in keys)
                    {
                        if (!itemCount.ContainsKey(key))
                        {
                            itemCount.Add(key, 0);
                        }
                    }
                }
            }

            Current = true;
        }

        /// <summary>
        /// 对特征进行匹配
        /// </summary>
        /// <param name="args">特征值</param>
        /// <param name="ignore">计算时是否对未知值进行忽略</param>
        /// <returns>计算结果集</returns>
        public Dictionary<string, ResInfo> Matching(string[] args, bool? ignore)
        {
            logInfo = string.Empty;
            Log("开始计算...\n");

            var reses = new Dictionary<string, ResInfo>(); // 初始化空结果集
            foreach (var resVal in resCountDic.Keys)
            {
                ResInfo resInfo; // 初始化结果
                resInfo.resVal = resVal; // 存储当前结论值
                resInfo.itemVals = new List<string>(); // 初始化特征对应的值
                resInfo.val = 1;
                float temp = resCountDic[resVal] * 1f / amount; // P(结论值)
                Log($"P({resVal})={temp}\n");
                resInfo.val *= temp; // 第一个相乘的值为P(结论值)
                var itemCount = resItemCountDic[resVal];
                var count = 0;

                foreach (var arg in args)
                {
                    count++;
                    if (!itemKeys[count].Contains(arg))
                    {
                        if ((bool) ignore || string.IsNullOrWhiteSpace(arg)) // 对未知特征进行忽略并输出忽略信息
                        {
                            Log($"w:检测到潜在的错误输入值[{Titles[count]}]=[{arg}]，已进行忽略\n");
                            continue;
                        }

                        itemCount.TryAdd(arg, 0); // 强行添加不存在的键值表，因没有相关特征，故计数必然为0
                    }

                    temp = itemCount[arg] * 1f / resCountDic[resVal]; // P(特征的值|结论值)
                    Log($"P({arg}|{resVal})={temp}\n");
                    resInfo.itemVals.Add(arg);
                    resInfo.val *= temp; // 对每一个P(特征的值|结论值)进行累乘
                }

                reses.Add(resVal, resInfo); // 添加计算的结果
            }

            ResInfo truth; // 初始化结论
            truth.val = 0;
            truth.resVal = "无法判断";
            foreach (var key in reses.Keys) // 获取结论
            {
                ResInfo res = reses[key];
                if (res.val > truth.val)
                    truth = res;
                Log($"是{key}的概率为P({res.resVal}|{string.Join(',', res.itemVals)})={res.val * 100}%\n");
            }

            Log($"判断的结果：{Titles[^1]}为{truth.resVal}\n");
            return reses; // 返回结论集
        }

        /// <summary>
        /// 输出信息
        /// </summary>
        /// <param name="info"></param>
        void Log(string info)
        {
            logInfo += info;
            onLog(logInfo, info);
        }
    }
}